package com.pubinfo.passbook.engine.factory.impl;

import com.pubinfo.passbook.common.entity.TEngineComputeChain;
import com.pubinfo.passbook.common.entity.TEngineComputeUnit;
import com.pubinfo.passbook.common.exception.ServiceException;
import com.pubinfo.passbook.common.model.http.engine.ComputeParam;
import com.pubinfo.passbook.common.service.engine.EngineComputeChainService;
import com.pubinfo.passbook.common.service.engine.EngineComputeUnitService;
import com.pubinfo.passbook.engine.cell.ComputeUnit;
import com.pubinfo.passbook.common.model.http.engine.ComputeChainParam;
import com.pubinfo.passbook.engine.cell.hook.medicine.payinformation.SaveReimbursableHook;
import com.pubinfo.passbook.engine.cell.logic.*;
import com.pubinfo.passbook.engine.cell.logic.medicine.*;
import com.pubinfo.passbook.engine.cell.logic.medicine.payinformation.IsLaborUnit;
import com.pubinfo.passbook.engine.cell.logic.medicine.payinformation.TimeoutUnit;
import com.pubinfo.passbook.engine.cell.logic.medicine.payinformation.VerticalAndNotInsuranceUnit;
import com.pubinfo.passbook.engine.cell.single.*;
import com.pubinfo.passbook.engine.chain.transfer.ComputeChainTransfer;
import com.pubinfo.passbook.engine.chain.transfer.TransferUtils;
import com.pubinfo.passbook.engine.chain.ComputeChain;
import com.pubinfo.passbook.engine.factory.ComputeChainFactory;
import com.pubinfo.passbook.engine.factory.ComputeUnitFactory;
import com.pubinfo.passbook.engine.constants.ComputeUnitType;
import com.pubinfo.passbook.engine.constants.ComputeUnitSymbol;
import com.pubinfo.passbook.engine.factory.ParamenterProcessorFactory;
import com.pubinfo.passbook.engine.process.ParamenterProcessor;
import com.pubinfo.passbook.engine.utils.ValidateUtils;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.stream.Collectors;

/**
 * @author zhouch
 * @date 2021/12/14 8:44
 */
@Component
@Slf4j
public class DefaultComputeUnitFactroy implements ComputeUnitFactory {

    @Resource
    EngineComputeUnitService unitService;

    @Resource
    EngineComputeChainService chainService;

    /**
     * 两个工厂是互相依赖的，使用懒加载来避免依赖注入循环的问题。
     * <p>
     * TIP: 计算链工厂需要装载计算单元，计算单元工厂中的计算单元参数可能是一个计算链引用，需要重新在计算链工厂里制造，递归工厂。
     */
    @Resource
    @Lazy
    ComputeChainFactory chainFactory;


    @Resource
    ParamenterProcessorFactory processorFactory;

    /**
     * 计算链体组装
     * 解析链体内的计算链参数返回实际工作的计算单元。
     *
     * @param chainParamItem
     * @param targetChainType
     * @return
     */
    @Override
    public ComputeUnit<?, ?> getComputor(@NonNull ComputeChainParam chainParamItem, @NonNull String targetChainType) {

        /**
         * 1) 从数据库中查询计算单元ID，获得计算单元信息，然后根据标识符初始化计算单元，最后装载计算单元参数后返回。
         * ===============================================================================
         * 2) 如果该计算单元是逻辑类型，需要使用计算链初工厂始化来初始化2条判断计算链。
         * ===============================================================================
         * 2) 如果是计算单元ID为一个计算链的引用，那么使用转换器变为一个计算单元。
         * 3) 如果计算单元类型不兼容，则需要先检查转换器里有没有能把该计算链类型转化为目标计算链类型的，如果有的话则继续。
         */

        if (chainParamItem.isChain()) {
            return makeComputeUnitByChainRef(chainParamItem, targetChainType);
        } else {
            return makeComputeUnit(chainParamItem, targetChainType);
        }
    }


    /**
     * 初始化计算单元
     *
     * @param chainParamItem
     * @param targetChainType
     * @return
     */
    private ComputeUnit<?, ?> makeComputeUnit(ComputeChainParam chainParamItem, String targetChainType) {

        log.info("正在初始化计算链参数: {}", chainParamItem);

        // 获得计算单元表中的信息
        TEngineComputeUnit unitInfo = unitService.getById(chainParamItem.getUnitId());

        if (unitInfo == null) {
            throw new ServiceException("不存在该计算单元信息(可能被意外删除) ID: " + chainParamItem.getUnitId());
        }

        // 实例化计算单元
        ComputeUnit<?, ?> unit = getComputeUnit(unitInfo);

        // 装载参数
        loadParamentProcess(unit, chainParamItem.getParamBody());

        // 如果是逻辑计算单元...
        // 需要重新初始化左右两条计算链
        if (StringUtils.equals(unit.getUnitType(), ComputeUnitType.LOGIC)) {
            ComputeLogicUnit<?, ?> logicUnit = (ComputeLogicUnit<?, ?>) unit;

            log.info("发现逻辑计算单元 {} , 初始化分支计算链...", logicUnit.getClass().getName());

            // 初始化2条分支计算链 (这两条计算链不是引用)
            logicUnit.setSuccessComputeChain(chainFactory.getComputor(chainParamItem.getSuccessful(), targetChainType));
            logicUnit.setFailedComputeChain(chainFactory.getComputor(chainParamItem.getFailed(), targetChainType));
        }

        return unit;
    }

    /**
     * 通过计算链初始化计算单元
     *
     * @param chainParamItem
     * @param targetChainType
     * @return
     */
    private ComputeUnit<?, ?> makeComputeUnitByChainRef(ComputeChainParam chainParamItem, String targetChainType) {

        log.info("正在初始化计算链引用：{}", chainParamItem);

        // 获得计算链元数据
        TEngineComputeChain chainInfo = chainService.getById(chainParamItem.getChainId());

        if (chainInfo == null) {
            throw new ServiceException("不存在该计算链信息(可能被意外删除) ID: " + chainParamItem.getChainId());
        }

        // 根据元数据初始化计算链
        ComputeChain<?, ?> chain = chainFactory.getComputor(chainInfo);

        // 转化计算链为兼容的计算单元
        ComputeChainTransfer<?, ?, ?> compatibleChain = TransferUtils.convertTargetChainType(chain, targetChainType);

        // 为空就是不兼容的类型
        if (compatibleChain == null) {
            throw new ServiceException("不兼容的计算链: 名称=" + chainInfo.getName() + " 原类型=" + chainInfo.getType() + " 目标类型:" + targetChainType);
        }

        return compatibleChain;
    }

    /**
     * 载计算单元的参数处理器
     * <p>
     * 根据参数列表来初始化和装载每个参数插槽所对应的参数处理器
     *
     * @param unit
     * @param params
     * @return
     */
    private void loadParamentProcess(ComputeUnit<?, ?> unit, Collection<ComputeParam> params) {

        // 在参数元数据上检查参数名称和参数类型和计算单元参数插槽的类型匹配
        if (false == ValidateUtils.checkComputeUnitParamsAvailable(unit, params)) {
            throw new ServiceException("计算单元参数插槽不匹配： 计算单元类型：" + unit.getUnitType() + " 计算单元名称：" + unit.getName() + "参数列表" + params.toString());
        }

        log.info("正在解析计算单元参数处理器：{}", params);

        /**
         * 将参数解析为参数处理器
         */
        Collection<? extends ParamenterProcessor<?, ?>> processors = params
                .stream()
                .map(
                        t -> processorFactory.getProcessor(t, unit.getChainType())).collect(Collectors.toList()
                );

        // 在参数处理器上检查参数名称和参数类型以及计算链类型和计算单元插槽是否匹配
        if (false == ValidateUtils.checkComputeUnitProcessorAvailable(unit, processors)) {

            // 参数处理器信息
            String processorsInfo = processors
                    .stream()
                    .map(ParamenterProcessor::toString).collect(Collectors.joining());

            throw new ServiceException("计算单元参数处理器插槽不匹配： 计算单元类型：" + unit.getUnitType() + " 计算单元名称：" + unit.getName() + "\n" +
                    "不匹配的参数处理器信息:" + processorsInfo);
        }

        // 装载参数处理器
        unit.loadProcessors(processors);
    }

    /**
     * 该方法通过计算单元的类型来分发给下面的工厂方法
     *
     * @param unitInfo
     * @return
     */
    private ComputeUnit<?, ?> getComputeUnit(TEngineComputeUnit unitInfo) {

        log.info("计算单元信息: 名称: {}, 类型: {}, 描述: {}, Symbol: {}",
                unitInfo.getName(), unitInfo.getType(), unitInfo.getDescription(), unitInfo.getSymbol());

        // 根据类型得到不同计算单元的实例
        switch (unitInfo.getType()) {
            case ComputeUnitType.SINGLE:
                return getSingleTypeComputeUnit(unitInfo.getSymbol().trim());
            case ComputeUnitType.LOGIC:
                return getLogicTypeComputeUnit(unitInfo.getSymbol().trim());
            case ComputeUnitType.HOOK:
                return getHookTypeComputeUnit(unitInfo.getSymbol().trim());
            default:
                throw new ServiceException("未知的计算单元类型：" + unitInfo.getType());
        }
    }

    /**
     * 获得单向计算单元根据标识符
     *
     * @param symbol
     * @return
     */
    private ComputeUnit<?, ?> getSingleTypeComputeUnit(String symbol) {
        switch (symbol) {
            case ComputeUnitSymbol.Single.ADD:
                return new AddUnit<>();
            case ComputeUnitSymbol.Single.MINUS:
                return new MinusUnit<>();
            case ComputeUnitSymbol.Single.BE_MINUS:
                return new BeMinusUnit<>();
            case ComputeUnitSymbol.Single.MULTIPLY:
                return new MultiplyUnit<>();
            case ComputeUnitSymbol.Single.DIVIDE:
                return new DivideUnit<>();
            case ComputeUnitSymbol.Single.ZERO:
                return new ZeroUnit<>();
            case ComputeUnitSymbol.Single.ASSIGN:
                return new AssignUnit<>();
            default:
                throw new ServiceException("未知的数值类型计算单元：" + symbol);
        }
    }

    private ComputeUnit<?, ?> getLogicTypeComputeUnit(String symbol) {
        switch (symbol) {
            // 通用类型
            case ComputeUnitSymbol.Logic.THAN:
                return new ThanUnit<>();
            case ComputeUnitSymbol.Logic.THAN_CUSTOM:
                return new ThanCustomUnit<>();
            case ComputeUnitSymbol.Logic.THAN_EQUAL:
                return new ThanAndEqualUnit<>();
            case ComputeUnitSymbol.Logic.THAN_EQUAL_CUSTOM:
                return new ThanAndEqualCustomUnit<>();
            case ComputeUnitSymbol.Logic.LESS:
                return new LessUnit<>();
            case ComputeUnitSymbol.Logic.LESS_CUSTOM:
                return new LessCustomUnit<>();
            case ComputeUnitSymbol.Logic.LESS_EQUAL:
                return new LessAndEqualUnit<>();
            case ComputeUnitSymbol.Logic.LESS_EQUAL_CUSTOM:
                return new LessAndEqualCustomUnit<>();
            case ComputeUnitSymbol.Logic.EQUAL:
                return new EqualUnit<>();

            // 医药费类
            case ComputeUnitSymbol.Logic.Medicine.JOB_WHETHER:
                return new JobWhetherUnit();
            case ComputeUnitSymbol.Logic.Medicine.RETIRE_WHETHER:
                return new RetireWhetherUnit();
            case ComputeUnitSymbol.Logic.Medicine.EARLY_RETIRE_WHETHER:
                return new EarlyRetireWhetherUnit();
            case ComputeUnitSymbol.Logic.Medicine.OVER_WHETHER:
                return new OverWhetherUnit();
            case ComputeUnitSymbol.Logic.Medicine.NORMAL_HOSPITAL:
                return new NormalHospitalUnit();
            case ComputeUnitSymbol.Logic.Medicine.SPECIAL_HOSPITAL:
                return new SpecialHospitalUnit();
            case ComputeUnitSymbol.Logic.Medicine.SPECIAL_USER:
                return new SpecialUserUnit();
            case ComputeUnitSymbol.Logic.Medicine.SPECIAL_DISEASES:
                return new SpecialDiseasesUnit();
            case ComputeUnitSymbol.Logic.Medicine.SPECIAL_DISEASES_OR_HOSPITAL:
                return new SpecialDiseasesOrHospitalUnit();
            case ComputeUnitSymbol.Logic.Medicine.OUTPATIENT:
                return new OutpatientlUnit();
            case ComputeUnitSymbol.Logic.Medicine.AGE_THEN:
                return new AgeThanUnit();
            case ComputeUnitSymbol.Logic.Medicine.AGE_LESS:
                return new AgeLessUnit();
            case ComputeUnitSymbol.Logic.Medicine.AGE_BETWEEN:
                return new AgeBetweenUnit();
            case ComputeUnitSymbol.Logic.Medicine.WORK_AGE_BETWEEN:
                return new WorkAgeBetweenUnit();
            case ComputeUnitSymbol.Logic.Medicine.WORK_AGE_LESS:
                return new WorkAgeLessUnit();
            case ComputeUnitSymbol.Logic.Medicine.WORK_AGE_THAN:
                return new WorkAgeThanUnit();
            case ComputeUnitSymbol.Logic.Medicine.JOB_AND_WORK_AGE_BETWEEN:
                return new JobAndWorkAgeBetweenUnit();
            case ComputeUnitSymbol.Logic.Medicine.JOB_AND_WORK_AGE_LESS:
                return new JobAndWorkAgeLessUnit();
            case ComputeUnitSymbol.Logic.Medicine.JOB_AND_WORK_AGE_THAN:
                return new JobAndWorkAgeThanUnit();
            case ComputeUnitSymbol.Logic.Medicine.RETIRE_AND_WORK_AGE_THAN:
                return new RetireAndAgeThanUnit();

            // 医药费子单类
            case ComputeUnitSymbol.Logic.PayInformation.VERTICAL_AND_NOT_INSURANCE:
                return new VerticalAndNotInsuranceUnit();
            case ComputeUnitSymbol.Logic.PayInformation.IS_LABOR:
                return new IsLaborUnit();
            case ComputeUnitSymbol.Logic.PayInformation.TIMEOUT:
                return new TimeoutUnit();

            default:
                throw new ServiceException("未知的逻辑类型计算单元：" + symbol);
        }
    }

    private ComputeUnit<?, ?> getHookTypeComputeUnit(String symbol) {
        switch (symbol) {

            // 医药费子单
            case ComputeUnitSymbol.Hook.PayInformation.SAVE_REIMBURSABLE:
                return new SaveReimbursableHook();

            default:
                throw new ServiceException("未知的钩子类型计算单元：" + symbol);
        }
    }

}
